﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using DatabaseGroup.SSIS.SourceAdapters.Parser;

namespace DatabaseGroup.UnitTests.SSIS.SourceAdapters.Parser
{
    [TestClass()]
    public class FieldParserTest
    {
        private TestContext _testContextInstance;

        public TestContext TestContext
        {
            get
            {
                return _testContextInstance;
            }
            set
            {
                _testContextInstance = value;
            }
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty arg")]
        public void BuildParserWithSingleDelimiter_EmptyArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithSingleDelimiter(string.Empty);
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty 1st arg")]
        public void BuildParserWithSingleDelimiterAndQualifier_EmptyArg1stArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithSingleDelimiterAndQualifier(string.Empty, "\"");
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty 2nd arg")]
        public void BuildParserWithSingleDelimiterAndQualifier_EmptyArg2ndArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithSingleDelimiterAndQualifier(",", string.Empty);
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty 1st arg")]
        public void BuildParserWithTwoDelimiters_EmptyArg1stArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithTwoDelimiters(string.Empty, "\r\n");
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty 2nd arg")]
        public void BuildParserWithTwoDelimiters_EmptyArg2ndArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithTwoDelimiters(",", string.Empty);
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty 2nd arg")]
        public void BuildParserWithTwoDelimitersAndQualifier_EmptyArg2ndArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",", string.Empty, "\"");
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty 1st arg")]
        public void BuildParserWithTwoDelimitersAndQualifier_EmptyArg1stArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(string.Empty, "\r\n", "\"");
        }
        
        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with empty 3rd arg")]
        public void BuildParserWithTwoDelimitersAndQualifier_EmptyArg3rdArg_ThrowArgumentNullException()
        {
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",", "\r\n", string.Empty);
        }

        [TestMethod()]
        public void BuildParserWithTwoDelimitersAndQualifier_DefaultInitialization_ReturnInstanceWithDefaultState()
        {
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",", "\r\n", "\"");
            Assert.AreEqual<bool>(false, target.RowDelimiterMatch, "Expected row delimiter match state to be false immediately after object initialization");
            Assert.AreEqual<string>(string.Empty, target.CurrentText, "Expected current text to be empty immediately after object initialization");
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithSingleDelimiter_ReturnExpectedFieldValues()
        {
            IFileReader reader = FileReaderTest.GetReader("A,B,C,1,2,3");
            FieldParser target = FieldParser.BuildParserWithSingleDelimiter(",");

            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B");
            VerifySuccessfulFieldParsing(target, reader, "C");
            VerifySuccessfulFieldParsing(target, reader, "1");
            VerifySuccessfulFieldParsing(target, reader, "2");
            VerifySuccessfulFieldParsing(target, reader, "3");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseLinesWithSingleDelimiter_ReturnExpectedFieldValues()
        {
            IFileReader reader = FileReaderTest.GetReader("A,B,C\r\n1,2,3");
            FieldParser target = FieldParser.BuildParserWithSingleDelimiter("\r\n");

            VerifySuccessfulFieldParsing(target, reader, "A,B,C");
            VerifySuccessfulFieldParsing(target, reader, "1,2,3");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseLinesWithTwoDelimiters_ReturnExpectedFieldValuesAndRowMatchState()
        {
            IFileReader reader = FileReaderTest.GetReader("A,B,C\r\n1,2,3");
            FieldParser target = FieldParser.BuildParserWithTwoDelimiters(",", "\r\n");

            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B");
            VerifySuccessfulLastRowFieldParsing(target, reader, "C");
            VerifySuccessfulFieldParsing(target, reader, "1");
            VerifySuccessfulFieldParsing(target, reader, "2");
            VerifySuccessfulFieldParsing(target, reader, "3");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseLinesWithTwoDelimitersAndTextQualifier_ReturnExpectedFieldValuesAndRowMatchState()
        {
            IFileReader reader = FileReaderTest.GetReader("A,\"B,C\r\n1\",2,3\r\n1,2,3,\"4\"");
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",", "\r\n", "\"");

            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B,C\r\n1");
            VerifySuccessfulFieldParsing(target, reader, "2");
            VerifySuccessfulLastRowFieldParsing(target, reader, "3");
            VerifySuccessfulFieldParsing(target, reader, "1");
            VerifySuccessfulFieldParsing(target, reader, "2");
            VerifySuccessfulFieldParsing(target, reader, "3");
            VerifySuccessfulFieldParsing(target, reader, "4");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseLinesWithSingleDelimitersAndTextQualifier_ReturnExpectedFieldValues()
        {
            IFileReader reader = FileReaderTest.GetReader("\"A,B,C\r\n1\",2,3\r\n1,2,3,\"4\"");
            FieldParser target = FieldParser.BuildParserWithSingleDelimiterAndQualifier("\r\n", "\"");

            VerifySuccessfulFieldParsing(target, reader, "A,B,C\r\n1,2,3");
            VerifySuccessfulFieldParsing(target, reader, "1,2,3,\"4\"");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseLinesWithTwoDelimitersAndTextQualifierWithBadData_ReturnExpectedFieldValuesAndRowMatchState()
        {
            IFileReader reader = FileReaderTest.GetReader("\"A\",\"B\",\"C\" \r\n\"1\",\"2\" ,3");
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",", "\r\n", "\"");

            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B");
            // field data is everything between delimiters, 
            // NOT everything between text qualifiers
            VerifySuccessfulLastRowFieldParsing(target, reader, "C ");
            VerifySuccessfulFieldParsing(target, reader, "1");
            VerifySuccessfulFieldParsing(target, reader, "2 ");
            VerifySuccessfulFieldParsing(target, reader, "3");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithSingleDelimitersAndTextQualifier_ReturnExpectedFieldValues()
        {
            IFileReader reader = FileReaderTest.GetReader("A,\"B,C,D\",1,2,3,\"4\"");
            FieldParser target = FieldParser.BuildParserWithSingleDelimiterAndQualifier(",", "\"");

            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B,C,D");
            VerifySuccessfulFieldParsing(target, reader, "1");
            VerifySuccessfulFieldParsing(target, reader, "2");
            VerifySuccessfulFieldParsing(target, reader, "3");
            VerifySuccessfulFieldParsing(target, reader, "4");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithSingleDelimitersAndEscapedTextQualifier_ReturnExpectedFieldValues()
        {
            IFileReader reader = FileReaderTest.GetReader("A,\"B,\"\"C\"\",D\",1,2,3,\"4\"");
            FieldParser target = FieldParser.BuildParserWithSingleDelimiterAndQualifier(",", "\"");

            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B,\"C\",D");
            VerifySuccessfulFieldParsing(target, reader, "1");
            VerifySuccessfulFieldParsing(target, reader, "2");
            VerifySuccessfulFieldParsing(target, reader, "3");
            VerifySuccessfulFieldParsing(target, reader, "4");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithSingleDelimitersAndMultiCharTextQualifier_ReturnExpectedFieldValues()
        {
            IFileReader reader = FileReaderTest.GetReader("A,:\"::\"B,:\"::\":C:\"::\":,D:\":,1,:\"2,3,:\":4:\":");
            FieldParser target = FieldParser.BuildParserWithSingleDelimiterAndQualifier(",", ":\":");

            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, ":\"B,:\":C:\":,D");
            VerifySuccessfulFieldParsing(target, reader, "1");
            VerifySuccessfulFieldParsing(target, reader, ":\"2");
            VerifySuccessfulFieldParsing(target, reader, "3");
            VerifySuccessfulFieldParsing(target, reader, "4");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseLinesWithMultiCharDelimitersAndTextQualifier_ReturnExpectedFieldValuesAndRowMatchState()
        {
            IFileReader reader = FileReaderTest.GetReader("A,.,\"B,C,D\",.,1,2,3,.,4,\r\na,.,b");
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",.,", "\r\n", "\"");
            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B,C,D");
            VerifySuccessfulFieldParsing(target, reader, "1,2,3");
            VerifySuccessfulLastRowFieldParsing(target, reader, "4,");
            VerifySuccessfulFieldParsing(target, reader, "a");
            VerifySuccessfulFieldParsing(target, reader, "b");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        public void ParseNext_ParseLinesWithMultiCharDelimitersAndTextQualifierWithBadData_ReturnExpectedFieldValuesAndRowMatchState()
        {
            IFileReader reader = FileReaderTest.GetReader("A,.,\"B,C,D\",.1,2,3,.,4,\r\na,.,b");
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",.,", "\r\n", "\"");
            VerifySuccessfulFieldParsing(target, reader, "A");
            VerifySuccessfulFieldParsing(target, reader, "B,C,D,.1,2,3");
            VerifySuccessfulLastRowFieldParsing(target, reader, "4,");
            VerifySuccessfulFieldParsing(target, reader, "a");
            VerifySuccessfulFieldParsing(target, reader, "b");

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        [TestMethod()]
        [ExpectedException(typeof(ParsingBufferOverflowException), "Expected ParsingBufferOverflowException to be thrown when called with too much data for buffer")]
        public void ParseNext_ColumnDataTooLargeForParseBuffer_ThrowParsingBufferOverflowException()
        {
            IFileReader reader = new FileReaderTestImpl("abcdefghjklmn", 100000);
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",", "\r\n", "\"");
            RowParsingContextTestImpl rowParsingContext = new RowParsingContextTestImpl();
            try
            {
                target.ParseNext(reader, rowParsingContext);
            }
            catch (ParsingBufferOverflowException ex)
            {
                Assert.AreEqual<int>(0, ex.ColumnIndex, "Expected column index to be zero when too much data provided for a successful column parse");
                throw ex;
            }
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithSingleEmbeddedTextQualifiers_ReturnExpectedFieldValues()
        {
            TestEmbeddedQualifiers("\"a,\"\"b\",c,d", new string [] {"a,\"b", "c", "d"});
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithMultiEmbeddedTextQualifiers1_ReturnExpectedFieldValues()
        {
            TestEmbeddedQualifiers("\"\"\"a,\"\"b,c\"\"\",d,e", new string[] { "\"a,\"b,c\"", "d", "e" });
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithMultiEmbeddedTextQualifiers2_ReturnExpectedFieldValues()
        {
            TestEmbeddedQualifiers("a,\"b,c\"\"\",e", new string[] { "a", "b,c\"", "e" });
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithEmptyEmbeddedTextQualifiers_ReturnExpectedFieldValues()
        {
            TestEmbeddedQualifiers("a,\"\"b,c", new string[] { "a", "b", "c" });
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithThreeEmbeddedTextQualifiers_ReturnExpectedFieldValues()
        {
            TestEmbeddedQualifiers("\"\"\"a,\"\"b,c\"\"\",d,e", new string[] { "\"a,\"b,c\"", "d", "e" });
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithEmbeddedTextQualifiers_ReturnExpectedFieldValuesWithIgnoredTextQualifiers()
        {
            TestEmbeddedQualifiers("a\"\"\"a,b,c", new string[] { "a\"\"\"a", "b", "c" });
        }

        [TestMethod()]
        public void ParseNext_ParseSingleLineWithEmbeddedTextQualifiers_ReturnExpectedFieldValuesWithIgnoredUnmatchedTextQualifiers()
        {
            TestEmbeddedQualifiers("\"a\"\"\"a\",b,c", new string[] { "a\"a\"", "b", "c" });
        }

        /// <summary>
        /// Initializes a preset FieldParser and parses all fields
        /// asserting they match they list of expected fields
        /// </summary>
        /// <param name="textToParse">The text to parse all the fields from</param>
        /// <param name="expectedFields">The expected fields to be returned from the parsed text</param>
        private static void TestEmbeddedQualifiers(string textToParse, string[] expectedFields)
        {
            IFileReader reader = FileReaderTest.GetReader(textToParse);
            FieldParser target = FieldParser.BuildParserWithTwoDelimitersAndQualifier(",", "\r\n", "\"");

            foreach (string fieldData in expectedFields)
            {
                VerifySuccessfulFieldParsing(target, reader, fieldData);
            }

            Assert.AreEqual<bool>(true, reader.IsEOF, "Expected file reader to be EOF after parsing all available fields");
        }

        /// <summary>
        /// Calls ParseNext and confirms that the resulting data is the same as
        /// the provided expected text
        /// </summary>
        /// <param name="target">The field parser to use for parsing</param>
        /// <param name="reader">The file reader to read the data from</param>
        /// <param name="expectedText">The expected field value</param>
        private static void VerifySuccessfulFieldParsing(FieldParser target, IFileReader reader, string expectedText)
        {
            target.ParseNext(reader);
            Assert.AreEqual<string>(expectedText, target.CurrentText, "Expected parsed text to match input text provided");
        }

        /// <summary>
        /// Calls ParseNext and confirms that the resulting data is the same as
        /// the provided expected text, along with confirming the row delimiter is matched
        /// </summary>
        /// <param name="target">The field parser to use for parsing</param>
        /// <param name="reader">The file reader to read the data from</param>
        /// <param name="expectedText">The expected field value</param>
        private static void VerifySuccessfulLastRowFieldParsing(FieldParser target, IFileReader reader, string expectedText)
        {
            target.ParseNext(reader);
            Assert.AreEqual<bool>(true, target.RowDelimiterMatch, "Expected to match delimiter indicating the end of row");
            Assert.AreEqual<string>(expectedText, target.CurrentText, "Expected parsed text to match input text provided");
        }
    }
}
